﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Pfz.RemoteGaming.Internal;
using Pfz.Threading;
using Pfz.Threading.Unsafe;

namespace Pfz.RemoteGaming
{
	/// <summary>
	/// A single animation object capable of animating multiple animations at the same time.
	/// </summary>
	public sealed class AnimationsAnimation:
		ThreadSafeDisposable,
		IEnumerator<bool>
	{
		#region Fields
			private List<IEnumerator<bool>> _animations = new List<IEnumerator<bool>>();
			private HashSet<ThreadedAnimation> _parallelAnimations = new HashSet<ThreadedAnimation>();
		#endregion

		#region Dispose
			/// <summary>
			/// Releases all active animations.
			/// </summary>
			protected override void Dispose(bool disposing)
			{
				if (disposing)
				{
					var parallelAnimations = _parallelAnimations;
					if (parallelAnimations != null)
					{
						_parallelAnimations = null;
						foreach(var animation in parallelAnimations)
							animation.Dispose();
					}

					var animations = _animations;
					if (animations != null)
					{
						_animations = null;
						foreach(var animation in animations)
							animation.Dispose();
					}
				}

				base.Dispose(disposing);
			}
		#endregion

		#region Methods
			#region Add
				#region IEnumerator
					/// <summary>
					/// Adds a new animation to be played together with the others.
					/// </summary>
					public void Add(IEnumerator<bool> animation)
					{
						if (animation == null)
							throw new ArgumentNullException("animation");

						lock(DisposeLock)
						{
							CheckUndisposed();

							_animations.Add(animation);
						}
					}
				#endregion
				#region IteratorAction
					/// <summary>
					/// Adds the given action as an animation.
					/// </summary>
					public void Add(IteratorAction animation)
					{
						if (animation == null)
							throw new ArgumentNullException("animation");

						ThreadedAnimation builtAnimation = new _UnsafeThreadedAnimationFromAction(animation);
						try
						{
							lock(DisposeLock)
							{
								CheckUndisposed();

								_animations.Add(builtAnimation);
							}
						}
						catch
						{
							builtAnimation.Dispose();
							throw;
						}
					}
				#endregion
			#endregion
			#region AddAsParallel
				#region IteratorAction
					/// <summary>
					/// Adds the given action as a parallel animation.
					/// As the name says, they can run in parallel, so do not count on their order.
					/// Parallel animations should not depend on other parallel animations or they can dead-lock.
					/// </summary>
					public void AddAsParallel(IteratorAction animation)
					{
						if (animation == null)
							throw new ArgumentNullException("animation");

						ThreadedAnimation builtAnimation = new _UnsafeThreadedAnimationFromAction(animation);
						try
						{
							lock(DisposeLock)
							{
								CheckUndisposed();

								_parallelAnimations.Add(builtAnimation);
							}
						}
						catch
						{
							builtAnimation.Dispose();
							throw;
						}
					}
				#endregion
				#region ThreadedAnimation
					/// <summary>
					/// Adds the the given threaded animation as a parallel one.
					/// As the name says, they can run in parallel, so do not count on their order.
					/// Parallel animations should not depend on other parallel animations or they can dead-lock.
					/// Note that ThreadedAnimations can be added as custom (non-parallel) animations.
					/// </summary>
					public void AddAsParallel(ThreadedAnimation animation)
					{
						if (animation == null)
							throw new ArgumentNullException("animation");

						lock(DisposeLock)
						{
							CheckUndisposed();

							_parallelAnimations.Add(animation);
						}
					}
				#endregion
			#endregion

			#region Update
				/// <summary>
				/// Updates (goes to the next frame of all animations).
				/// </summary>
				public bool Update()
				{
					IEnumerator<bool>[] animations = null;
					ThreadedAnimation[] parallelAnimations = null;
					lock(DisposeLock)
					{
						if (_animations == null)
							return false;

						if (_animations.Count > 0)
							animations = _animations.ToArray();

						if (_parallelAnimations.Count > 0)
							parallelAnimations = _parallelAnimations.ToArray();
					}

					List<ThreadedAnimation> parallelToRemove = null;
					if (parallelAnimations != null)
					{
						foreach(var animation in parallelAnimations)
						{
							if (animation.BeginUpdate())
								continue;

							if (parallelToRemove == null)
								parallelToRemove = new List<ThreadedAnimation>();

							parallelToRemove.Add(animation);
						}

						foreach(var animation in parallelAnimations)
						{
							if (animation.EndUpdate())
								continue;

							if (parallelToRemove == null)
								parallelToRemove = new List<ThreadedAnimation>();

							parallelToRemove.Add(animation);
						}
					}

					List<IEnumerator<bool>> toRemove = null;
					if (animations != null)
					{
						int count = animations.Length;
						for(int i=0; i<count; i++)
						{
							var animation = animations[i];

							bool animationOk = false;
							try
							{
								animationOk = animation.MoveNext();
							}
							catch(RemoteGameObjectDisposedException)
							{
							}

							if (!animationOk)
							{
								animation.Dispose();

								if (toRemove == null)
									toRemove = new List<IEnumerator<bool>>();

								toRemove.Add(animation);
							}
						}
					}

					if (toRemove != null || parallelToRemove != null)
					{
						lock(DisposeLock)
						{
							if (_animations == null)
								return false;

							if (parallelToRemove != null)
							{
								foreach(var animation in parallelToRemove)
								{
									_parallelAnimations.Remove(animation);
									animation.Dispose();
								}
							}

							if (toRemove != null)
							{
								int count = toRemove.Count;
								for(int i=count-1; i>=0; i--)
								{
									var animation = toRemove[i];
									_animations.Remove(animation);
								}
							}
						}
					}

					return true;
				}
			#endregion
		#endregion

		#region IEnumerator
			bool IEnumerator<bool>.Current
			{
				get
				{
					return true;
				}
			}

			object IEnumerator.Current
			{
				get
				{
					return true;
				}
			}

			bool IEnumerator.MoveNext()
			{
				return Update();
			}

			void IEnumerator.Reset()
			{
				lock(DisposeLock)
				{
					var animations = _animations;
					if (animations == null)
						throw new ObjectDisposedException(GetType().FullName);

					foreach(var animation in animations)
						animation.Reset();
				}
			}
		#endregion
	}
}
